\documentclass[a4paper,twoside]{article}
\usepackage{blindtext}
\usepackage{geometry}

% Chinese support
\usepackage[UTF8, scheme = plain]{ctex}

% Page margin layout
\geometry{left=2.3cm,right=2cm,top=2.5cm,bottom=2.0cm}

\usepackage{listings}
\usepackage{xcolor}
\usepackage{geometry}
\usepackage{amsmath}
\usepackage{float}
\usepackage{hyperref}

\usepackage{graphics}
\usepackage{graphicx}
\usepackage{subfigure}
\usepackage{epsfig}
\usepackage{float}

% \usepackage{algorithm}
\usepackage[noend]{algpseudocode}

\usepackage{booktabs}
\usepackage{threeparttable}
\usepackage{longtable}
\usepackage{listings}
\usepackage{tikz}
\usepackage{multicol}

% cite package, to clean up citations in the main text. Do not remove.
\usepackage{cite}
\usepackage{dirtree} % 绘制目录树
\usepackage{color,xcolor}

%% The amssymb package provides various useful mathematical symbols
\usepackage{amssymb}
%% The amsthm package provides extended theorem environments
\usepackage{amsthm}
\usepackage{amsfonts}
\usepackage{enumerate}
\usepackage{enumitem}
\usepackage{listings}
\usepackage{appendix}
\usepackage[ruled,linesnumbered]{algorithm2e}
\usepackage{indentfirst}
\setlength{\parindent}{2em} % Make two letter space in the first paragraph
\usepackage{setspace}
\linespread{1.5} % Line spacing setting
\usepackage{siunitx}
\setlength{\parskip}{0.5em} % Paragraph spacing setting

\renewcommand{\figurename}{图}
\renewcommand{\lstlistingname}{代码} 
\renewcommand{\tablename}{表格}
\renewcommand{\contentsname}{目录}

\graphicspath{ {images/} }
\newcommand{ \upcitep}[1]{\textsuperscript{\textsuperscript{\citep{#1} } }} % 设置上标显示参考文献编号的命令
%%%%%%%%%%%%%
\newcommand{\StudentNumber}{}  % Fill your student number here
\newcommand{\StudentName}{刘本宸}  % Replace your name here
\newcommand{\PaperTitle}{Linux环境下多进程模拟机票代理期末大作业}  % Change your paper title here
\newcommand{\PaperType}{Linux内核分析-大作业技术文档} % Replace the type of your report here
\newcommand{\Date}{2023年6月10日}
\newcommand{\College}{信息学院}
\newcommand{\CourseName}{Linux内核分析}
%%%%%%%%%%%%%

%% Page header and footer setting
\usepackage{fancyhdr}
\usepackage{lastpage}
\pagestyle{fancy}
\fancyhf{}
% This requires the document to be twoside
\fancyhead[LO]{\texttt{\StudentName }}
\fancyhead[LE]{\texttt{\StudentNumber}}
\fancyhead[C]{\texttt{\PaperTitle }}
\fancyhead[R]{\texttt{第{\thepage} 页，共 \pageref*{LastPage} 页}}

\title{\PaperTitle}
\author{\StudentName}
\date{\Date}


\lstset{
	basicstyle          =   \sffamily,          % 基本代码风格
	keywordstyle        =   \bfseries,          % 关键字风格
	commentstyle        =   \rmfamily\itshape,  % 注释的风格，斜体
	stringstyle         =   \ttfamily,  % 字符串风格
	flexiblecolumns,                % 别问为什么，加上这个
	numbers             =   left,   % 行号的位置在左边
	showspaces          =   false,  % 是否显示空格，显示了有点乱，所以不现实了
	numberstyle         =   \zihao{-5}\ttfamily,    % 行号的样式，小五号，tt等宽字体
	showstringspaces    =   false,
	captionpos          =   t,      % 这段代码的名字所呈现的位置，t指的是top上面
	frame               =   lrtb,   % 显示边框
}

\lstdefinestyle{PythonStyle}{
	language        =   Python, % 语言选Python
	basicstyle      =   \zihao{-5}\ttfamily,
	numberstyle     =   \zihao{-5}\ttfamily,
	keywordstyle    =   \color{blue},
	keywordstyle    =   [2] \color{teal},
	stringstyle     =   \color{magenta},
	commentstyle    =   \color{red}\ttfamily,
	breaklines      =   true,   % 自动换行，建议不要写太长的行
	columns         =   fixed,  % 如果不加这一句，字间距就不固定，很丑，必须加
	basewidth       =   0.5em,
}

\lstdefinestyle{CppStyle}{
	language        =   c++,
	basicstyle      =   \zihao{-5}\ttfamily,
	numberstyle     =   \zihao{-5}\ttfamily,
	keywordstyle    =   \color{blue},
	keywordstyle    =   [2] \color{teal},
	stringstyle     =   \color{magenta},
	commentstyle    =   \color{red}\ttfamily,
	breaklines      =   true,   % 自动换行，建议不要写太长的行
	columns         =   fixed,  % 如果不加这一句，字间距就不固定，很丑，必须加
	basewidth       =   0.5em,
}

\lstdefinestyle{makeStyle}{
	language        =   make,
	basicstyle      =   \zihao{-5}\ttfamily,
	numberstyle     =   \zihao{-5}\ttfamily,
	keywordstyle    =   \color{blue},
	keywordstyle    =   [2] \color{teal},
	stringstyle     =   \color{magenta},
	commentstyle    =   \color{red}\ttfamily,
	breaklines      =   true,   % 自动换行，建议不要写太长的行
	columns         =   fixed,  % 如果不加这一句，字间距就不固定，很丑，必须加
	basewidth       =   0.5em,
}


\algnewcommand\algorithmicinput{\textbf{Input:}}
\algnewcommand\algorithmicoutput{\textbf{Output:}}
\algnewcommand\Input{\item[\algorithmicinput]}%
\algnewcommand\Output{\item[\algorithmicoutput]}%
\renewcommand{\abstractname}{\textbf{\zihao{3}摘\quad 要}}
\usetikzlibrary{positioning, shapes.geometric}


\begin{document}

\makeatletter % change default title style
\renewcommand*\maketitle{%
    \begin{center}
        \bfseries  % title 
        {\LARGE \@title \par}  % LARGE typesetting
        \vskip 1em  %  margin 1em
            {\global\let\author\@empty}  % no author information
            {\global\let\date\@empty}  % no date
        \thispagestyle{empty}   %  empty page style
    \end{center}%
    \setcounter{footnote}{0}%
}
\makeatother

\thispagestyle{empty}

\vspace*{1cm}

\begin{figure}[h]
    \centering
    \includegraphics[width=4.0cm]{logo.png}
\end{figure}

\vspace*{1cm}

\begin{center}
    \Huge{\textbf{\PaperType}}

    \Large{\PaperTitle}
\end{center}

\begin{table}[h]
    \centering
    \begin{Large}
        \renewcommand{\arraystretch}{1.5}
        \begin{tabular}{p{3cm} p{5cm}<{\centering}}
            姓 \quad 名 & \StudentName   \\
            \hline
            学 \quad 号 & 22920202200764 \\
            \hline
            日 \quad 期 & \Date          \\
            \hline
            学 \quad 院 & \College       \\
            \hline
            课程名称    & \CourseName    \\
            \hline
            教 \quad 师 & 陈毅东         \\
        \end{tabular}
    \end{Large}
\end{table}
\newpage
\tableofcontents

\newpage
\section{问题重述}

本次大作业的基本目标是，使用linux多线程模拟航班的用户进程进行订票操作。

在作业中需要模拟航班服务器对来到的需求进行处理，同时也需要我们模拟代理进行代理信息的处理。同时使用sleep函数来模拟信号在
代理和服务器之间的传播时延。

在本次项目中，我们使用的输入文件的格式如下：

\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.7 \linewidth]{images/input_format.png}
    \caption{输入文件格式}
\end{figure}

在客户端程序会对文件进行读入和解析，之后客户端对每个请求都会进行处理，在处理的时候会返回成功与否的信息，如果成功会返回成功信息，如果
失败会说明失败原因并且驳回客户端的请求，在所有的客户请求信息都被处理之后，客户端会对客户的预定和购买操作进行总结，并且格式化的
输出每一个航班的预定信息。（在这里需要注意一个地方就是：一个被乘客A预定的机票只能由乘客A进行购买操作）

\newpage
\section{Linux系统下的IPC机制介绍}

IPC（Interprocess Communication，进程间通信）机制是操作系统提供的一组机制，用于实现不同进程之间的数据传递和通信。
进程是计算机中正在执行的程序的实例。在一个多进程的操作系统中，每个进程拥有自己独立的地址空间和资源，彼此之间是相互隔离的。然而，有时进程之间需要进行协作和数据交换，以实现任务的完成。IPC机制解决了多个进程需要协作、共享信息和资源的问题。

IPC机制提供了一种标准化的方式，使得不同进程能够进行有效的通信。它允许进程在同一台计算机上传递数据、共享资源、进行同步和互斥操作等。比如进程可以通过IPC机制实现任务的分工与协作，共享数据和资源，提高系统的并发性和效率。
使用IPC机制，进程可以通过共享内存、管道、消息队列、信号量、套接字等方式进行通信。每种IPC机制都有自己的特点和适用场景，接下来我们将对已有的IPC机制分别进行介绍。


\subsection{管道 Pipe}
Linux的IPC机制中的管道（Pipe），通常指的是匿名管道（Anonymous Pipe）。管道是一种半双工的通信机制，用于在父进程和子进程之间进行单向的数据传输。管道可以用于进程间的通信，特别是在父进程创建子进程后，它们之间需要传递数据或通过数据流进行协作的情况下。
\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.6 \linewidth]{images/pipework.png}
    \caption{管道的工作机制}
\end{figure}

管道是一种简单而实用的进程间通信机制，通过它父进程可以将数据写入管道的写入端，子进程可以从管道的读取端读取这些数据。管道使用先进先出（FIFO）的方式，保证了数据的顺序传递。然而，管道是单向的，一旦数据从写入端传输到读取端后，就无法再通过管道返回到写入端。
管道在进程间传递数据时，可以用于进程间的同步。当写入端写入数据时，如果读取端没有准备好接收数据，写入端会被阻塞，直到读取端准备好。使用管道的过程包括创建管道，读写数据，以及合适的同步操作。管道是一种常用且简单的IPC机制，适用于父子进程之间的单向数据传输。
管道的使用非常简单，主要包括以下几个步骤：
\begin{enumerate}[itemsep= 2 pt]
    \item 使用pipe()系统调用创建管道，它会返回两个文件描述符，一个用于读取，一个用于写入。
    \item 在父进程中，使用fork()创建子进程。
    \item 父进程使用写入端的文件描述符向管道中写入数据。
    \item 子进程使用读取端的文件描述符从管道中读取数据。
\end{enumerate}
在本次实验中没有用到管道这一IPC机制。
\subsection{共享内存 Shared Memory}

在Linux的IPC机制中，共享内存（Shared Memory）是一种高效的进程间通信机制。它允许多个进程共享同一块内存区域，从而实现快速的数据交换和共享。

\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.7 \linewidth]{images/shmwork.png}
    \caption{共享内存的工作机制}
\end{figure}
使用共享内存的一般工作流程如下：首先，通过系统调用函数创建共享内存区域，在申请内存块的时候需要预先确定内存块的大小，并将其关联到各个进程的地址空间中。
创建共享内存时需要指定内存大小，操作系统会为其分配对应的内存页。然后，多个进程可以直接读写共享内存，进行数据交换和共享。
在读写操作时，需要适当地使用同步机制（如信号量、互斥锁）来保证数据的一致性和避免竞态条件。最后，当所有关联的进程解除了对共享内存的映射关系后，共享内存将被释放，可以被其他进程重新创建和使用。

在本次实验中，我们将使用共享内存的方法来存储每一个航班的座位信息，座位表，状态表等等。所有代理给出的请求操作都要对这块共享内存进行操作。
同时在多进程操作的前提之下，我们要使用多进程的方法对共享内存进行互斥的操作。
\subsection{信号量 Semaphore}
Linux的IPC机制中的信号量（Semaphore）是一种用于进程间同步和互斥的机制。
信号量允许进程对共享资源的访问进行控制，确保多个进程能够按照特定的顺序和条件进行操作。

信号量的主要概念和功能如下：信号量是一个计数器，用于表示可用的资源数量。它可以被多个进程同时访问和操作。每个进程在访问共享资源之前都需要检查信号量的值，根据信号量的值决定是否可以进入临界区或访问共享资源。通过适当的信号量操作，进程可以实现同步和互斥，避免竞态条件和资源争用。

使用信号量的一般工作流程如下：首先，需要创建信号量并初始化它的初始值。每个进程在需要访问共享资源之前，首先尝试对信号量进行P（Proberen）操作，该操作会将信号量的值减一。如果信号量的值大于等于零，则表示资源可用，进程可以进入临界区或访问共享资源。如果信号量的值小于零，则表示资源不可用，进程需要等待，直到其他进程释放资源。在完成共享资源的访问后，进程需要对信号量进行V（Verhogen）操作，将信号量的值加一，表示释放资源。

在本次实验中我们也用到了信号量的相关机制，比如当多个进程互斥的访问共享内存时，我们将使用信号量这一机制保证多进程对内存空间的操作不会出现错误或者段错误。
\subsection{消息队列 Massage Queue}
消息队列是一种进程间通信的机制，它通过存储和传递消息实现进程间的数据交换和协作。消息队列允许进程将消息发送到队列中，并允许其他进程从队列中接收和处理这些消息。它提供了异步通信、解耦和可靠性的特性。通过使用消息队列，发送方和接收方进程可以独立存在，并且不需要同时运行。

\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.7 \linewidth]{images/msgqueuework.png}
    \caption{消息队列的工作机制}
\end{figure}

消息队列实现了进程间的解耦，发送方进程可以将消息发送到队列中，而不需要直接知道接收方进程的存在和状态。接收方进程可以从队列中读取消息，并根据需要进行处理。消息队列在实现分布式系统、任务调度、事件驱动等方面具有广泛的应用。同时，消息队列通常提供可靠的消息传递机制，即使接收方暂时不可用，消息也会被保存在队列中，确保消息不会丢失。

消息队列是用的类似队列的先进先出的运行机制，可以实现进程间的异步通信。但是消息队列的容量是有限的，当队列已满时，发送方进程可能需要等待，直到有足够的空间可用。类似地，当队列为空时，接收方进程可能需要等待，直到有消息可用。这种等待状态被称为阻塞。

\subsection{套接字 Socket}
套接字是一种实现网络和本地进程通信的同步机制，它通过创建、绑定和监听连接请求，以及建立连接和数据传输等步骤实现通信。进程可以使用套接字读写数据，并通过调用关闭函数终止连接。套接字基于不同的协议和地址族，可以满足各种通信需求，包括网络通信和本地进程间通信。

\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.6 \linewidth]{images/socketwork.png}
    \caption{消息队列的工作机制}
\end{figure}

在计算机网络课程的相关实验中已经深入探索了socket通信的种种功能，在这里就不详述了。
\pagebreak
\section{大作业实现思路与代码实现}
首先要进行文件的读入，读入文件之后我们先使用解析函数对文本进行解析并且根据文本信息对航班个数、航班信息、代理信息以及代理请求等等配置信息对
相应的Plane类，Agent类和Flights类进行初始化。在初始化过后我们要对共享内存进行初始化，并且将共享内存的相关信息与当前进程关联起来。

而后我们将对代理进行初始化，主要是保存各个代理的异步操作时间以及每一个请求的请求方式。最后我们使用fork的方式生成若干个子进程来，
模拟多个代理对航班也就是共享内存的互斥操作。最后在主进程中会等待每一个子进程完成自己请求后。对各个航班信息进行最后的总结并且将总结信息格式化输出。
\subsection{飞机与航班模块}
\subsubsection{Plane 类}
Plane类在整个程序中扮演着表示飞机、管理座位信息和提供座位状态查询功能的角色。它是飞机的一个抽象表示，用于存储和管理与飞机相关的数据和功能。
对于一个特定的请求Plane类会修改自身的数据库，并且也提供查询座位状态信息的接口。

Plane 类可以类比于SQL操作中的数据表（table）的概念。来自代理的所有请求操作最后都会落实到Plane类的数据上。
\begin{lstlisting}[style=Cppstyle, caption = Plane类的定义]
struct Info{
    char status[10];
    char seat_num[10];
    char passenger_name[10];
};

class Plane{
    public:
        char plane_name[50];
        int col, row;
        int passenger_cnt;
        Info table[MAXCNT];
        Plane();
        string check_seat_status(string seat_num);
};    
\end{lstlisting}

\subsubsection{Flights 类}
Flights类主要是完成了对于飞机序列的初始化、对于请求的处理以及最后的总结展示功能。Flights类管理着多个Plane对象，并且封装着多个
操作Plane类的函数，是对代理对象请求做出反应的主要对象。以下是Flights类的具体定义。
\begin{lstlisting}[style=Cppstyle, caption = Flights类的定义]
class Flights
{
public:
    Plane flights_seq[MAXSIZE];
    int f_cnt;
    Flights();
    void show();
    void do_request(struct operation, int wait_time);
private:
    string reserve_seat(Plane &plane, string seat_num, string passenger_name);
    string sell_ticket(Plane &plane, string seat_num, string passenger_name);
    string cancel_reservation(Plane &plane, string seat_num, string passenger_name);
    string check_passenger(string passenger_name);
};   
\end{lstlisting}
\subsection{代理模块}
\subsubsection {Agent 类}
Agent类是代理模块中最主要的功能实现代码段，主要是负责存储从文本文件解析来的请求，存储每一种请求的延迟时间以及有顺序的
对Flights类进行请求的访问。

Agent类的定义代码段如下：
\begin{lstlisting}[style=Cppstyle, caption = Agent类的定义]
class Agent{
public:
    // 每一个操作存储为一个vector中的元素
    vector<operation> op_seq;
    Agent();
    int get_time_by_op_name(const string &op_name);
private:
    // 延迟时间的定义
    int reserve_time;
    int ticket_time;
    int cancel_time;
    int check_time;
};
\end{lstlisting}
\subsection{文本解析模块}
文本解析模块基本是基于字符串处理函数和文件操作函数的。比如我们先使用C++的fstream库函数对文件进行读取。然后对每一个行进行解析，最后可以得到格式化的信息，从这些
信息出发我们可以初始化飞机的名字，飞机座位表以及代理信息和代理请求。

\begin{lstlisting}[style=Cppstyle, caption = 文本读入函数]
// 读取文本文件的每一行
vector<string> readLinesFromFile(const string &filename){
    vector<string> lines;
    ifstream file(filename);
    if(file.is_open()){
        string line;
        while(getline(file, line))
            lines.push_back(line);
        file.close();
    }
    return lines;
}
\end{lstlisting}
同时从文本来得到最后代理和飞机初始化信息需要一般的流程控制操作，在这些操作之后我们就可以开始进行请求处理了，操作流程如下：
\begin{lstlisting}[style=Cppstyle, caption = 代理与飞机对象初始化函数]
// 解析并初始化飞机信息
int parseFlights(const vector<string> &lines, Plane *flight_seq){
    bool isFlightsSection = false;
    int cnt = 0;
    Plane plane;
    for (const string &line : lines)
    {
        if (line == "begin flights"){
            isFlightsSection = true;
        }
        else if (line == "end flights"){
            isFlightsSection = false;
        }
        else if (isFlightsSection){
           // 根据给定的文本信息初始化飞机信息
           // 将初始化后的飞机信息添加到flight_seq中
           // 将飞机计数器cnt自增1
        }
    }
    return cnt;
}
// 解析并初始化代理信息
vector<Agent> parseAgents(const vector<string> &lines){
    vector<Agent> agents;        // 存储代理对象的容器
    bool isAgentSection = false; // 标志变量，用于确定当前是否处于代理部分的解析阶段
    Agent agent;                 // 当前代理对象
    for (const string &line : lines){
        if (line == "begin agent"){
            // 创建新的代理对象
            isAgentSection = true;
            agent = Agent(); 
        }
        else if (line == "end agent"){
            // 完成代理信息解析，将代理对象添加到容器中
            isAgentSection = false;
            agents.push_back(agent); 
        }
        else if (isAgentSection){
            // 对每一行的输入信息解析为具体的操作
            // 将操作压入一个新的agent的操作栈中
        }
    }
    return agents; // 返回代理对象的容器
}
\end{lstlisting}

\newpage
\subsection{主函数流程处理模块}
main函数主要完成了以下几个功能：首先，它从名为input.txt的文本文件中读取内容，并将每一行存储在一个字符串向量中。
接下来，它创建了一个信号量和一个共享内存，用于在不同进程之间进行同步和数据共享。
然后，它解析并初始化了飞机的信息，并将其映射到共享内存空间中。
接着，它解析了代理的信息并存储在一个向量中。然后，它创建了多个子进程，每个子进程负责执行一个代理的操作。
子进程按顺序执行其操作序列，并通过信号量控制对共享内存的访问。
主进程等待所有子进程执行完毕后，再次访问共享内存并格式化地显示飞机的信息。
最后，它清理了使用的资源，输出程序结束的信息。

下面是主函数的程序流程图：
\begin{figure}[htbp]
    \centering
    \begin{tikzpicture}[node distance=1.5cm, every node/.style={rectangle, draw, text centered, rounded corners}]
        % Nodes
        \node (start) {Start};
        \node (read) [below of=start] {Read lines from file};
        \node (create) [below of=read] {Create semaphore and shared memory};
        \node (map) [below of=create] {Map flight information to shared memory};
        \node (init) [below of=map] {Parse and initialize flight information};
        \node (detach) [below of=init] {Detach shared memory};
        \node (parse) [below of=detach] {Parse and initialize agent information};
        \node (fork) [below of=parse] {Fork processes};
        \node (child) [below left of=fork, xshift=-3cm] {Child process};
        \node (execute) [below of=child] {Execute operations exclusively};
        \node (exit) [below of=execute] {Exit child process};
        % \node (parent) [below right of=fork, xshift=3cm] {Parent process};
        \node (wait) [below right of=fork, xshift=3cm] {Wait for child processes};
        \node (show) [below of=wait] {Show flight information};
        \node (clear) [below of=show] {Clear semaphore and shared memory};
        \node (end) [below of=clear] {End};

        % Arrows
        \draw [->] (start) -- (read);
        \draw [->] (read) -- (create);
        \draw [->] (create) -- (map);
        \draw [->] (map) -- (init);
        \draw [->] (init) -- (detach);
        \draw [->] (detach) -- (parse);
        \draw [->] (parse) -- (fork);
        \draw [->] (fork) -| node [near start, above] {Child process} (child);
        \draw [->] (fork) -| node [near start, above] {Parent process} (wait);
        \draw [->] (child) -- (execute);
        \draw [->] (execute) -- (exit);
        % \draw [->] (parent) -- (wait);
        \draw [->] (wait) -- (show);
        \draw [->] (show) -- (clear);
        \draw [->] (clear) -- (end);

    \end{tikzpicture}
    \caption{主函数程序流程图}
    \label{fig:main_flowchart}
\end{figure}

下面是主函数的代码实现:
\begin{lstlisting}[style=Cppstyle, caption = 主函数代码框架]
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <sys/wait.h>
#include "client.h"
#include "xmu_ipc.h"
using namespace std;

int sem_flight;
int shm_flight;
class Flights *my_flights;

int main(int argc, char *argv[])
{
        pid_t pid;
        if (argc == 1){
            cout << "Missing arguement, you need to add the file path of input file.";
            return 0;
        }
        // 读取文本文件的每一行
        string input_path(argv[1]);
        vector<string> lines = readLinesFromFile(input_path);
        // 创建信号量和共享内存
        sem_flight = new_sem((char *)"./", 254, 1, 1);
        shm_flight = new_shm((char *)"./", 111, sizeof(class Flights));
    
        // 将飞机们的信息映射到共享内存空间
        my_flights = (class Flights *)my_shmat(shm_flight);
    
        // 解析并初始化飞机信息
        my_flights->f_cnt = parseFlights(lines, my_flights->flights_seq);
    
        // 断开共享内存空间与进程地址空间的关联
        my_shmdt((const void *)my_flights);
    
        // 解析并初始化代理信息
        vector<Agent> agents = parseAgents(lines);
    
        auto agent_cnt = agents.size();
        int i, agent_id = 0;
        printf("--------------Begin-----------------\n");
        for (i = 1; i <= agent_cnt; i++)
            if ((pid = fork()) < 0){
                perror("Fork failed");
                exit(0);
            }
            else if (pid == 0){ // 子进程;
                agent_id = i;
                break;
            }

        if (agent_id != 0){ // 如果是子进程就进行请求操作
            Agent &this_agent = agents[agent_id - 1];
            for (auto op : this_agent.op_seq){
                sem_wait(sem_flight, 0);
                my_flights = (class Flights *)my_shmat(shm_flight);
                my_flights->do_request(op, this_agent.get_time_by_op_name(op.op_func));
                my_shmdt((const void *)my_flights);
                sem_signal(sem_flight, 0);
            }
            exit(agent_id);
        }
        else{ // 否则就是父进程，等待所有子进程结束操作之后进行父进程的show操作
            for (int j = 1; j <= agent_cnt; j++)    wait(NULL);
            my_flights = (class Flights *)my_shmat(shm_flight);
            my_flights->show();
            my_shmdt((const void *)my_flights);
            clear_sem(sem_flight);
            clear_shm(shm_flight);
            printf("--------------End-----------------\n");
        }
        return 0;
    } 
\end{lstlisting}
\subsection{文件目录结构}
\dirtree{%
    .1 .
    .2 src.
    .3 \textcolor{teal}{Makefile}.
    .3 client.cc.
    .3 client.h.
    .3 main.cc.
    .3 xmu\_ipc.cc.
    .3 xmu\_ipc.h.
    .2 testfile.
    .3 input.txt.
    .3 input2.txt.
}

\newpage
\section{运行结果与分析}

程序需要先使用Makefile进行C++文件的编译，然后再运行。在本次的实验中，我们让主程序接受输入测试文件的文件名，
针对输入参数，我们先对个数进行判断，如果参数个数少于我们需要的个数，会进行报错，否则我们会解析输入参数的路径
并且针对对应路径开始对文件进行解析。
\subsection{Makefile}
\paragraph{什么是Makefile？}
Makefile是一种文本文件，用于定义和管理软件项目的构建过程。它通常用于C、C++和其他编程语言的项目，但也可以用于其他类型的项目。
Makefile中包含一系列规则和命令，用于指定源代码文件如何编译、链接和构建可执行文件或库文件。它描述了项目的依赖关系和构建步骤，并提供了一种自动化构建系统的方式。

对于C++文件，我们使用Makefile进行源文件的链接和编译，在本次实验中选取了使用C++11作为编译标准，防止出现一些编译上面的warning。

下面是本实验的Makefile文件：

\begin{lstlisting}[style = makeStyle,caption = Makefile文件]
    CXX = g++
    CXXFLAGS = -std=c++11
    
    lbc_main: main.o xmu_ipc.o client.o
        $(CXX) $(CXXFLAGS) -o lbc_main main.o xmu_ipc.o client.o
    
    main.o: main.cc xmu_ipc.h client.h
        $(CXX) $(CXXFLAGS) -c main.cc
    
    xmu_ipc.o: xmu_ipc.cc xmu_ipc.h
        $(CXX) $(CXXFLAGS) -c xmu_ipc.cc
    
    client.o: client.cc client.h
        $(CXX) $(CXXFLAGS) -c client.cc
    
    .PHONY: clean
    clean:
        -rm lbc_main *.o   
\end{lstlisting}
\subsection{运行环境}
\begin{itemize}
    \item Ubuntu 20.04 LTS
    \item g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
    \item GNU Make 4.2.1
\end{itemize}
\newpage
\subsection{运行结果}
\subsubsection{命令行编译}
在上面的Makefile中我们已经定义好了，在文件目录之下我们在终端中键入make命令之后就可以执行编译命令，我们可以在文件的目录下看到lbc\_main的二进制可运行文件。
只需要运行该文件就可以看到程序运行的结果了。输入文件详见附录。

\subsubsection{运行结果}
我们先尝试使用第一个input来运行。在第一个输入文件中，我们先进行了飞机和其他对象的初始化，在初始化之后，我们进行了多进程互斥的操作，
来模拟飞机代理的机票操作。我们在操作的时候为了模拟时间停滞，我们针对输入的时间除以1000倍，以达到较快可以得到输出的目的。

在我们的程序模拟完成所有的操作之后，开始进行信息的输出，在输出的时候，我尝试在提醒信息之前加入了高亮信息，用来进行提示信息的
醒目输出。在代理的请求信息每一个处理的时候，我们都会对应输出一条处理的信息来模拟航空公司的订票系统针对代理系统请求的回复，回复都是有固定格式的
比如针对预定操作失败时我们会给出 "In plane ... Seat ... is already occupied"。
在最后我们给出了信息的总结输出，从图中可以看到，程序在最后的总结部分给出了每一个飞机对应的代号以及每一个乘客的位置预定以及购买信息。
\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.9 \linewidth]{images/demo1.png}
    \caption{Input1.txt 运行结果}
\end{figure}

我们再来看第二个测试文件的运行结果，第二个文件相对第一个文件较长因此算是压力测试。因为第一个运行结果我们已经给到了详细的解释所以针对第二个测试文件我们不做过多的解释。
\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.9 \linewidth]{images/demo2.png}
    \caption{Input2.txt 运行结果[1]}
\end{figure}

\begin{figure}[htbp]
    \centering
    \includegraphics[width = 0.9 \linewidth]{images/demo3.png}
    \caption{Input2.txt 运行结果[2]}
\end{figure}

\newpage
\section{总结}
本实验报告先解读了本次大作业的问题，回顾了在课程中的学到的一些Linux的IPC运行机制，这些IPC机制是我们本次实验运行的基石。
然后我们进行了大作业实现思路的解读和部分关键代码实现。最后我们给出了进行编译所需的Makefile以及编译命令。最后进行了两次
实验。在实验中我们可以看出我们的程序成功的运行而且对于一些异常也有很好的解决方法同时我们也进行了代码的调整，让输出更为美观。

在这次实验中，最难的就是整个程序的顶层设计，如何保证各个类之间相互耦合工作，是代码框架设计之初就要定好，否则最后的改动将会是
伤筋动骨的，同时也要保证整个的代码符合高内聚低耦合的特点，这样可以让我们在改动功能的时候不会陷入牵一发而动全身。同时在这门程序
设计中，我学到了不少查询资料的方式，比如在stackoverflow上面咨询大佬一些问题，使用ChatGPT来让它给我一些函数功能用法示例以及在
C++库函数中搜索函数定义等等。这些工具综合的运用在很大程度上面帮助了我这次的大作业。当然代码整体在调试之后很完美的运行起来。

最后感谢陈老师在这次课程中的辛苦付出，没有老师的付出我也不会学到如此多的知识。

\newpage
\begin{appendices}
    \section{测试输入文件}
    \begin{lstlisting}[style=Cppstyle, caption = input1.txt]
    begin flights
    Flight_1 5 5
    Flight_2 5 5
    Flight_3 6 7
    end flights
    begin agent
    reserve 1
    ticket 2
    cancel 3
    check_passenger 4
    reserve Flight_1 1B Bob
    ticket Flight_1 1B Bob
    end agent
    begin agent
    reserve 4
    ticket 3
    cancel 2
    check_passenger 1
    ticket Flight_1 1B Bob
    ticket Flight_2 1A Alice
    ticket Flight_1 1B John
    check_passenger Alice
    reserve Flight_3 1D Ben
    end agent        
        \end{lstlisting}

    \begin{lstlisting}[style=Cppstyle, caption = input2.txt]
    begin flights
    CA123 10 6
    MU456 8 4
    CZ789 12 8
    end flights
    begin agent
    reserve 5
    ticket 6
    cancel 3
    check_passenger 4
    reserve CA123 2A AAA
    reserve CA123 8C BBB
    ticket MU456 8C FFF
    check_passenger FFF
    end agent
    begin agent
    reserve 4
    ticket 6
    cancel 2
    check_passenger 7
    ticket CA123 2B TTT
    reserve CA123 2A DDD
    ticket CZ789 2A PPP
    check_passenger DDD
    end agent
    begin agent
    reserve 2
    ticket 10
    cancel 6
    check_passenger 5
    reserve CZ789 10D CCC
    reserve CA123 4E LLL
    ticket MU456 6B HHH
    end agent
    begin agent
    reserve 3
    ticket 5
    cancel 7
    check_passenger 3
    ticket CZ789 10C AAA
    check_passenger AAA
    end agent
    begin agent
    reserve 4
    ticket 1
    cancel 5
    check_passenger 4
    ticket MU456 5B RRR
    check_passenger RRR
    cancel MU456 5B RRR
    check_passenger RRR
    end agent
    \end{lstlisting}
\end{appendices}


\end{document}